
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * Copyright(C) Chris2018998,All rights reserved.
 *
 * Project owner contact:Chris2018998@tom.com.
 *
 * Project Licensed under Apache License v2.0.
 */
package org.stone.beecp.pool;

import javassist.*;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import static org.stone.tools.BeanUtil.BeeClassLoader;
import static org.stone.tools.CommonUtil.isBlank;

/**
 * An independent execution toolkit class to generate JDBC statement classes with javassist,
 * then write to class folder.
 *
 * @author Chris Liao
 * @version 1.0
 */
final class ProxyClassesGenerator {
    private static final String DefaultFolder = "stone/target/classes";

    /**
     * @param args take the first argument as classes generated output folder,otherwise take default folder
     * @throws Exception throw exception in generating process
     */
    public static void main(String[] args) throws Exception {
        String classesFolder = "";
        if (args != null && args.length > 0) classesFolder = args[0];
        if (isBlank(classesFolder)) classesFolder = DefaultFolder;
        ProxyClassesGenerator.writeProxyFile(classesFolder);
    }

    /**
     * write to disk folder
     *
     * @param folder classes generated will write to it
     * @throws Exception if failed to write file to disk
     */
    private static void writeProxyFile(String folder) throws Exception {
        CtClass[] ctClasses = ProxyClassesGenerator.createProxyClasses();
        for (CtClass ctClass : ctClasses) {
            ctClass.writeFile(folder);
        }
    }

    /**
     * create jdbc proxy classes
     *
     * @return a class array generated by javassist
     * <p>
     * create Classes:
     * org.stone.beecp.pool.ProxyConnection
     * org.stone.beecp.pool.ProxyStatement
     * org.stone.beecp.pool.ProxyPsStatement
     * org.stone.beecp.pool.ProxyCsStatement
     * org.stone.beecp.pool.ProxyResultSet
     * org.stone.beecp.pool.ProxyDatabaseMetaData
     * org.stone.beecp.pool.ProxyResultSetMetaData
     * <p>
     * org.stone.beecp.pool.ProxyConnection4L
     * org.stone.beecp.pool.ProxyStatement4L
     * org.stone.beecp.pool.ProxyPsStatement4L
     * org.stone.beecp.pool.ProxyCsStatement4L
     * @throws Exception if failed to generate class
     */
    private static CtClass[] createProxyClasses() throws Exception {
        ClassPool classPool = ClassPool.getDefault();
        classPool.importPackage("java.sql");
        classPool.importPackage("org.stone.beecp");
        classPool.importPackage("org.stone.beecp.pool");
        classPool.appendClassPath(new LoaderClassPath(BeeClassLoader));

        //************************************************************************************************************//
        //              1: Create proxy Classes without log Manager                                                   //                                                                                  //
        //************************************************************************************************************//

        //class1: org.stone.beecp.pool.ProxyConnection
        CtClass ctConnectionClass = classPool.get(Connection.class.getName());
        CtClass ctProxyConnectionBaseClass = classPool.get(ProxyConnectionBase.class.getName());
        CtClass ctPooledConnectionClass = classPool.get(org.stone.beecp.pool.PooledConnection.class.getName());
        CtClass ctBeeMethodExecutionLogCacheClass = classPool.get(MethodExecutionLogCache.class.getName());
        CtClass ctProxyConnectionClass = classPool.makeClass("org.stone.beecp.pool.ProxyConnection", ctProxyConnectionBaseClass);

        //constructor1
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{ctPooledConnectionClass}, ctProxyConnectionClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyConnectionClass.addConstructor(ctConstructor);
        //constructor2(for interceptor subclass)
        ctConstructor = new CtConstructor(new CtClass[]{ctPooledConnectionClass, ctBeeMethodExecutionLogCacheClass}, ctProxyConnectionClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyConnectionClass.addConstructor(ctConstructor);


        //class2: org.stone.beecp.pool.ProxyStatement
        CtClass ctLongClass = classPool.get("long");
        CtClass ctStringClass = classPool.get("java.lang.String");
        CtClass ctStatementClass = classPool.get(Statement.class.getName());
        CtClass ctProxyStatementBaseClass = classPool.get(ProxyStatementBase.class.getName());
        CtClass ctProxyStatementClass = classPool.makeClass("org.stone.beecp.pool.ProxyStatement", ctProxyStatementBaseClass);
        //constructor1
        CtClass[] statementCreateParamTypes = {
                ctStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass
        };
        ctConstructor = new CtConstructor(statementCreateParamTypes, ctProxyStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyStatementClass.addConstructor(ctConstructor);
        //constructor2(for interceptor subclass)
        CtClass[] tStatementCreateParamTypes = {//for interceptor
                ctStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass,
                ctLongClass,
                ctStringClass
        };
        ctConstructor = new CtConstructor(tStatementCreateParamTypes, ctProxyStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyStatementClass.addConstructor(ctConstructor);

        //class3: org.stone.beecp.pool.ProxyPsStatement
        CtClass ctPreparedStatementClass = classPool.get(PreparedStatement.class.getName());
        CtClass ctProxyPsStatementClass = classPool.makeClass("org.stone.beecp.pool.ProxyPsStatement", ctProxyStatementClass);
        ctProxyPsStatementClass.setInterfaces(new CtClass[]{ctPreparedStatementClass});
        //constructor1
        CtClass[] statementPsCreateParamTypes = {
                ctPreparedStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(statementPsCreateParamTypes, ctProxyPsStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyPsStatementClass.addConstructor(ctConstructor);
        //constructor2(for interceptor)
        CtClass[] tStatementPsCreateParamTypes = new CtClass[]{//for interceptor
                ctPreparedStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass,
                ctLongClass,
                ctStringClass
        };
        ctConstructor = new CtConstructor(tStatementPsCreateParamTypes, ctProxyPsStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyPsStatementClass.addConstructor(ctConstructor);

        //class4: org.stone.beecp.pool.ProxyCsStatement
        CtClass ctCallableStatementClass = classPool.get(CallableStatement.class.getName());
        CtClass ctProxyCsStatementClass = classPool.makeClass("org.stone.beecp.pool.ProxyCsStatement", ctProxyPsStatementClass);
        ctProxyCsStatementClass.setInterfaces(new CtClass[]{ctCallableStatementClass});
        //constructor1
        CtClass[] statementCsCreateParamTypes = {
                ctCallableStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(statementCsCreateParamTypes, ctProxyCsStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyCsStatementClass.addConstructor(ctConstructor);
        //constructor2(for interceptor)
        CtClass[] tStatementCsCreateParamTypes = {//for interceptor
                ctCallableStatementClass,
                ctProxyConnectionBaseClass,
                ctPooledConnectionClass,
                ctLongClass,
                ctStringClass
        };
        ctConstructor = new CtConstructor(tStatementCsCreateParamTypes, ctProxyCsStatementClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyCsStatementClass.addConstructor(ctConstructor);

        //class5: org.stone.beecp.pool.ProxyDatabaseMetaData
        CtClass ctDatabaseMetaDataClass = classPool.get(DatabaseMetaData.class.getName());
        CtClass ctProxyDatabaseMetaDataBaseClass = classPool.get(ProxyDatabaseMetaDataBase.class.getName());
        CtClass ctProxyDatabaseMetaDataClass = classPool.makeClass("org.stone.beecp.pool.ProxyDatabaseMetaData", ctProxyDatabaseMetaDataBaseClass);
        ctProxyDatabaseMetaDataClass.setModifiers(Modifier.FINAL);
        CtClass[] databaseMetaDataTypes = {
                ctDatabaseMetaDataClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(databaseMetaDataTypes, ctProxyDatabaseMetaDataClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyDatabaseMetaDataClass.addConstructor(ctConstructor);

        //class6: org.stone.beecp.pool.ProxyResultSet
        CtClass ctResultSetClass = classPool.get(ResultSet.class.getName());
        CtClass ctProxyResultSetBaseClass = classPool.get(ProxyResultSetBase.class.getName());
        CtClass ctProxyResultSetClass = classPool.makeClass("org.stone.beecp.pool.ProxyResultSet", ctProxyResultSetBaseClass);
        ctProxyResultSetClass.setModifiers(Modifier.FINAL);
        CtClass[] resultSetCreateParamTypes1 = {
                ctResultSetClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(resultSetCreateParamTypes1, ctProxyResultSetClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyResultSetClass.addConstructor(ctConstructor);
        CtClass[] resultSetCreateParamTypes = {
                ctResultSetClass,
                ctProxyStatementBaseClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(resultSetCreateParamTypes, ctProxyResultSetClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyResultSetClass.addConstructor(ctConstructor);

        //class7: org.stone.beecp.pool.ProxyResultSetMetaData
        CtClass ctResultSetMetaDataClass = classPool.get(ResultSetMetaData.class.getName());
        CtClass ctProxyResultSetMetaDataBaseClass = classPool.get(ProxyResultSetMetaDataBase.class.getName());
        CtClass ctProxyResultSetMetaDataClass = classPool.makeClass("org.stone.beecp.pool.ProxyResultSetMetaData", ctProxyResultSetMetaDataBaseClass);
        ctProxyResultSetMetaDataClass.setModifiers(Modifier.FINAL);
        CtClass[] resultSetMetaDataCreateParamTypes = {
                ctResultSetMetaDataClass,
                ctProxyResultSetBaseClass,
                ctPooledConnectionClass};
        ctConstructor = new CtConstructor(resultSetMetaDataCreateParamTypes, ctProxyResultSetMetaDataClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyResultSetMetaDataClass.addConstructor(ctConstructor);

        //************************************************************************************************************//
        //              2: add override methods to proxy classes                                                      //                                                                                  //
        //************************************************************************************************************//
        ProxyClassesGenerator.addMethodsToProxyConnectionClass(classPool, ctProxyConnectionClass, ctConnectionClass, ctProxyConnectionBaseClass, true);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyStatementClass, ctStatementClass, ctProxyStatementBaseClass, true);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyPsStatementClass, ctPreparedStatementClass, ctProxyStatementClass, true);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyCsStatementClass, ctCallableStatementClass, ctProxyPsStatementClass, true);
        ProxyClassesGenerator.addMethodsToProxyDatabaseMetaDataClass(classPool, ctProxyDatabaseMetaDataClass, ctDatabaseMetaDataClass, ctProxyDatabaseMetaDataBaseClass);
        ProxyClassesGenerator.addOverrideMethodsForProxyResultSetClass(classPool, ctProxyResultSetClass, ctResultSetClass, ctProxyResultSetBaseClass);
        ProxyClassesGenerator.addOverrideMethodsForProxyResultSetMetaDataClass(ctProxyResultSetMetaDataClass, ctResultSetMetaDataClass, ctProxyResultSetMetaDataBaseClass);

        //************************************************************************************************************//
        //              3: Create proxy Classes for interceptor                                                       //                                                                                  //
        //************************************************************************************************************//
        //class: org.stone.beecp.pool.ProxyConnection4L
        CtClass ctProxyConnection4LClass = classPool.makeClass("org.stone.beecp.pool.ProxyConnection4L", ctProxyConnectionClass);
        ctProxyConnection4LClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
        ctConstructor = new CtConstructor(new CtClass[]{ctPooledConnectionClass, ctBeeMethodExecutionLogCacheClass}, ctProxyConnection4LClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyConnection4LClass.addConstructor(ctConstructor);

        //class: org.stone.beecp.pool.ProxyStatement4l
        CtClass ctProxyStatement4LClass = classPool.makeClass("org.stone.beecp.pool.ProxyStatement4L", ctProxyStatementClass);
        ctProxyStatement4LClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
        ctConstructor = new CtConstructor(tStatementCreateParamTypes, ctProxyStatement4LClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyStatement4LClass.addConstructor(ctConstructor);

        //class: org.stone.beecp.pool.ProxyPsStatement4L
        CtClass ctProxyPsStatement4LClass = classPool.makeClass("org.stone.beecp.pool.ProxyPsStatement4L", ctProxyStatementClass);
        ctProxyPsStatement4LClass.setInterfaces(new CtClass[]{ctPreparedStatementClass});
        ctProxyPsStatement4LClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
        ctConstructor = new CtConstructor(tStatementPsCreateParamTypes, ctProxyPsStatement4LClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyPsStatement4LClass.addConstructor(ctConstructor);

        //class: org.stone.beecp.pool.ProxyCsStatement4L
        CtClass ctProxyCsStatement4LClass = classPool.makeClass("org.stone.beecp.pool.ProxyCsStatement4L", ctProxyCsStatementClass);
        ctProxyCsStatement4LClass.setInterfaces(new CtClass[]{ctCallableStatementClass});
        ctProxyCsStatement4LClass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
        ctConstructor = new CtConstructor(tStatementCsCreateParamTypes, ctProxyCsStatement4LClass);
        ctConstructor.setBody("{super($$);}");
        ctProxyCsStatement4LClass.addConstructor(ctConstructor);

        //************************************************************************************************************//
        //              4: add override methods to proxy-4L classes                                                    //                                                                                  //
        //************************************************************************************************************//
        ProxyClassesGenerator.addMethodsToProxyConnectionClass(classPool, ctProxyConnection4LClass, ctConnectionClass, ctProxyConnectionClass, false);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyStatement4LClass, ctStatementClass, ctProxyStatementClass, false);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyPsStatement4LClass, ctPreparedStatementClass, ctProxyStatementClass, false);
        ProxyClassesGenerator.addMethodsToProxyStatementClass(classPool, ctProxyCsStatement4LClass, ctCallableStatementClass, ctProxyPsStatementClass, false);

        //************************************************************************************************************//
        //               5: Fill code into methods to create proxy classes                                            //                                                                                  //
        //************************************************************************************************************//
        CtClass ctProxyObjectFactoryClass = classPool.get(ProxyConnectionFactory.class.getName());
        for (CtMethod method : ctProxyObjectFactoryClass.getDeclaredMethods()) {
            if ("createProxyConnection".equals(method.getName())) {
                method.setBody("{return new ProxyConnection($$);}");
                break;
            }
        }

        CtClass ctProxyObjectFactory4LClass = classPool.get(ProxyConnectionFactory4L.class.getName());
        for (CtMethod method : ctProxyObjectFactory4LClass.getDeclaredMethods()) {
            if ("createProxyConnection".equals(method.getName())) {
                method.setBody("{return new ProxyConnection4L($$,logCache);}");
                break;
            }
        }

        CtClass ctConnectionPoolStaticsClass = classPool.get(ConnectionPoolStatics.class.getName());
        for (CtMethod method : ctConnectionPoolStaticsClass.getDeclaredMethods()) {
            if ("createProxyResultSet".equals(method.getName())) {
                method.setBody("{return new ProxyResultSet($$);}");
                break;
            }
        }

        //************************************************************************************************************//
        //            6: return classes array for writing to disk                                                     //                                                                                  //
        //************************************************************************************************************//
        return new CtClass[]{
                ctProxyConnectionClass,
                ctProxyStatementClass,
                ctProxyPsStatementClass,
                ctProxyCsStatementClass,
                ctProxyDatabaseMetaDataClass,
                ctProxyResultSetClass,
                ctProxyResultSetMetaDataClass,
                ctProxyObjectFactoryClass,
                ctConnectionPoolStaticsClass,

                ctProxyConnection4LClass,
                ctProxyStatement4LClass,
                ctProxyPsStatement4LClass,
                ctProxyCsStatement4LClass,
                ctProxyObjectFactory4LClass};
    }

    //find out methods,which not need add proxy
    private static HashSet<String> findMethodsNotNeedProxy(CtClass baseClass) {
        HashSet<String> notNeedAddProxyMethods = new HashSet<>(16);
        for (CtMethod ctSuperClassMethod : baseClass.getMethods()) {
            int modifiers = ctSuperClassMethod.getModifiers();
            if ((!Modifier.isAbstract(modifiers) && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)))
                    || Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers) || Modifier.isNative(modifiers)) {
                notNeedAddProxyMethods.add(ctSuperClassMethod.getName() + ctSuperClassMethod.getSignature());
            }
        }
        return notNeedAddProxyMethods;
    }

    /**
     * create connection statement class
     * *
     *
     * @param classPool                   javassist class pool
     * @param ctConnectionClassProxyClass connection implemented subclass will be generated
     * @param ctConnectionClass           connection interface in javassist class pool
     * @param ctConBaseClass              super class extend by 'ctConnectionClassProxyClass'
     * @throws Exception some error occurred
     */
    private static void addMethodsToProxyConnectionClass(ClassPool classPool, CtClass ctConnectionClassProxyClass, CtClass ctConnectionClass, CtClass ctConBaseClass, boolean noTrace) throws Exception {
        List<CtMethod> linkedList = new ArrayList<>(50);
        CtClass ctStatementClass = classPool.get(Statement.class.getName());
        CtClass ctPreparedStatementClass = classPool.get(PreparedStatement.class.getName());
        CtClass ctCallableStatementClass = classPool.get(CallableStatement.class.getName());
        CtClass ctDatabaseMetaDataClass = classPool.get(DatabaseMetaData.class.getName());

        if (noTrace) {//no log collection
            HashSet<String> notNeedAddProxyMethods = findMethodsNotNeedProxy(ctConBaseClass);
            ProxyClassesGenerator.resolveInterfaceMethods(ctConnectionClass, linkedList, notNeedAddProxyMethods);
            StringBuilder methodBuffer = new StringBuilder(50);
            for (CtMethod ctMethod : linkedList) {
                String methodName = ctMethod.getName();
                CtMethod newCtMethod = CtNewMethod.copy(ctMethod, ctConnectionClassProxyClass, null);
                newCtMethod.setModifiers(Modifier.PUBLIC);

                methodBuffer.delete(0, methodBuffer.length());
                methodBuffer.append("{");
                boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
                if (existsSQLException) methodBuffer.append("  try{");
                CtClass ctResultType = ctMethod.getReturnType();
                if (ctResultType == ctStatementClass) {
                    methodBuffer.append("return new ProxyStatement(raw.").append(methodName).append("($$),this,p);");
                } else if (ctResultType == ctPreparedStatementClass) {
                    methodBuffer.append("return new ProxyPsStatement(raw.").append(methodName).append("($$),this,p);");
                } else if (ctResultType == ctCallableStatementClass) {
                    methodBuffer.append("return new ProxyCsStatement(raw.").append(methodName).append("($$),this,p);");
                } else if (ctResultType == ctDatabaseMetaDataClass) {
                    methodBuffer.append("return new ProxyDatabaseMetaData(raw.").append(methodName).append("($$),p);");
                } else if (methodName.equals("close")) {
                    continue;
                } else if (ctResultType == CtClass.voidType) {
                    methodBuffer.append("raw.").append(methodName).append("($$);");
                } else {
                    methodBuffer.append("return raw.").append(methodName).append("($$);");
                }

                if (existsSQLException)
                    methodBuffer.append(" }catch(SQLException e){ p.checkSQLException(e);throw e;}");
                methodBuffer.append("}");
                newCtMethod.setBody(methodBuffer.toString());
                ctConnectionClassProxyClass.addMethod(newCtMethod);
            }
        } else {//for log collect
            ProxyClassesGenerator.getConnectionStatementMethods(classPool, ctConnectionClass, linkedList);
            StringBuilder methodBuffer = new StringBuilder(50);
            for (CtMethod ctMethod : linkedList) {
                String methodName = ctMethod.getName();
                CtMethod newCtMethod = CtNewMethod.copy(ctMethod, ctConnectionClassProxyClass, null);
                newCtMethod.setModifiers(Modifier.PUBLIC);
                methodBuffer.delete(0, methodBuffer.length());

                //1: method start
                methodBuffer.append("{");
                boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
                CtClass ctResultType = ctMethod.getReturnType();
                if (ctResultType == ctStatementClass) {
                    //1.1: add 'try'
                    if (existsSQLException) methodBuffer.append(" try{");
                    //1.2: new proxy object
                    methodBuffer.append("return new ProxyStatement4L(raw.").append(methodName).append("($$),this,p,0L,null);");
                    //1.3: add 'catch' code snippet
                    if (existsSQLException) {
                        methodBuffer.append(" }catch(SQLException e){");
                        methodBuffer.append(" p.checkSQLException(e);throw e;}");
                    }
                } else if (ctResultType == ctPreparedStatementClass) {//Connection.prepareStatement(...)
                    //2.1: add 'logCache.beforeCall'
                    String methodSignature = getCtMethodSignature("Connection.", ctMethod);
                    CtClass[] parameterTypes = ctMethod.getParameterTypes();
                    int methodParameterSize = parameterTypes.length;
                    if (methodParameterSize == 0) {
                        methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Preparation,").append(methodSignature).append(",null,null,null);");
                    } else {
                        methodBuffer.append("Object[]parameters = new Object[]{");
                        for (int i = 0; i < methodParameterSize; i++) {
                            if (i > 0) methodBuffer.append(",");
                            methodBuffer.append(getConvertType("$" + (i + 1), parameterTypes[i]));
                        }
                        methodBuffer.append("};");
                        methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Preparation,").append(methodSignature).append(",parameters,null,null);");
                    }

                    //2.2: add 'try'
                    if (existsSQLException) methodBuffer.append(" try{");

                    //2.3: add 'raw PreparedStatement invocation'
                    methodBuffer.append(ctResultType.getName()).append(" re=raw.").append(methodName).append("($$);");

                    //2.4: add 'logCache.afterCall'
                    methodBuffer.append("logCache.afterCall(re,0L,null,log);");

                    //2.5: add 'proxy object creation' code
                    methodBuffer.append("return new ProxyPsStatement4L(re,this,p,log.getEndTime() - log.getStartTime(),$1);");

                    //2.6: add 'catch'
                    if (existsSQLException) {
                        methodBuffer.append(" }catch(SQLException e){");
                        methodBuffer.append(" p.checkSQLException(e);");
                        methodBuffer.append(" logCache.afterCall(e,0L,null,log);");
                        methodBuffer.append(" throw e;}");
                    }

                } else if (ctResultType == ctCallableStatementClass) {//Connection.prepareCall(...)
                    //3.1: add 'logCache.beforeCall'
                    String methodSignature = getCtMethodSignature("Connection.", ctMethod);
                    CtClass[] parameterTypes = ctMethod.getParameterTypes();
                    int methodParameterSize = parameterTypes.length;
                    if (methodParameterSize == 0) {
                        methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Preparation,").append(methodSignature).append(",null,null,null);");
                    } else {
                        methodBuffer.append("Object[]parameters = new Object[]{");
                        for (int i = 0; i < methodParameterSize; i++) {
                            if (i > 0) methodBuffer.append(",");
                            methodBuffer.append(getConvertType("$" + (i + 1), parameterTypes[i]));
                        }
                        methodBuffer.append("};");
                        methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Preparation,").append(methodSignature).append(",parameters,null,null);");
                    }

                    //3.2: add 'try'
                    if (existsSQLException) methodBuffer.append(" try{");

                    //3.3: add 'raw CallableStatement invocation'
                    methodBuffer.append(ctResultType.getName()).append(" re=raw.").append(methodName).append("($$);");

                    //3.4: add 'logCache.afterCall'
                    methodBuffer.append("logCache.afterCall(re,0L,null,log);");

                    //3.5: dd 'proxy object creation' code
                    methodBuffer.append("return new ProxyCsStatement4L(re,this,p,log.getEndTime()-log.getStartTime(),$1);");

                    //3.6: add 'catch'
                    if (existsSQLException) {
                        methodBuffer.append(" }catch(SQLException e){");
                        methodBuffer.append(" p.checkSQLException(e);");
                        methodBuffer.append(" logCache.afterCall(e,0L,null,log);");
                        methodBuffer.append(" throw e;}");
                    }
                }
                methodBuffer.append("}");
                newCtMethod.setBody(methodBuffer.toString());
                ctConnectionClassProxyClass.addMethod(newCtMethod);
            }
        }
    }

    private static void addMethodsToProxyStatementClass(ClassPool classPool, CtClass statementProxyClass, CtClass ctStatementClass, CtClass ctStatementSuperClass, boolean noTrace) throws Exception {
        List<CtMethod> linkedList = new ArrayList<>(50);

        if (noTrace) {
            HashSet<String> notNeedAddProxyMethods = findMethodsNotNeedProxy(ctStatementSuperClass);
            ProxyClassesGenerator.resolveInterfaceMethods(ctStatementClass, linkedList, notNeedAddProxyMethods);

            CtClass ctResultSetClass = classPool.get(ResultSet.class.getName());
            StringBuilder methodBuffer = new StringBuilder(50);

            String rawName = "raw.";
            if ("java.sql.PreparedStatement".equals(ctStatementClass.getName())) {
                rawName = "((PreparedStatement)raw).";
            } else if ("java.sql.CallableStatement".equals(ctStatementClass.getName())) {
                rawName = "((CallableStatement)raw).";
            }

            for (CtMethod ctMethod : linkedList) {
                methodBuffer.delete(0, methodBuffer.length());
                methodBuffer.append("{");

                boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
                if (existsSQLException) methodBuffer.append("  try{");

                String methodName = ctMethod.getName();
                CtClass ctResultType = ctMethod.getReturnType();

                //1: update dirty flag
                boolean executeMethod = (methodName.startsWith("execute"));
                if (executeMethod) methodBuffer.append("p.commitDirtyInd=!p.curAutoCommit;");

                //2: method call on raw object
                if (ctResultType == CtClass.voidType)
                    methodBuffer.append(rawName).append(methodName).append("($$);");
                else
                    methodBuffer.append(ctResultType.getName()).append(" r=").append(rawName).append(methodName).append("($$);");

                //3: update last access time
                if (executeMethod) methodBuffer.append("p.lastAccessTime=System.currentTimeMillis();");

                //4: return block
                if (ctResultType == ctResultSetClass) {
                    methodBuffer.append("return new ProxyResultSet(r,this,p);");
                } else if (ctResultType != CtClass.voidType) {
                    methodBuffer.append("return r;");
                }

                //5: catch block
                if (existsSQLException)
                    methodBuffer.append(" }catch(SQLException e){ p.checkSQLException(e);throw e;}");

                //end
                methodBuffer.append("}");

                CtMethod newCtMethod = CtNewMethod.copy(ctMethod, statementProxyClass, null);
                newCtMethod.setBody(methodBuffer.toString());
                statementProxyClass.addMethod(newCtMethod);
            }//for end
        } else {//sql execution
            getStatementExecutionMethods(ctStatementClass, linkedList);
            CtClass ctResultSetClass = classPool.get(ResultSet.class.getName());
            StringBuilder methodBuffer = new StringBuilder(50);

            String rawName = "raw.";
            String statementType;
            if ("java.sql.PreparedStatement".equals(ctStatementClass.getName())) {
                statementType = "PreparedStatement.";
                rawName = "((PreparedStatement)raw).";
            } else if ("java.sql.CallableStatement".equals(ctStatementClass.getName())) {
                statementType = "CallableStatement.";
                rawName = "((CallableStatement)raw).";
            } else {
                statementType = "Statement.";
            }

            for (CtMethod ctMethod : linkedList) {//all method names start with 'execute'
                methodBuffer.delete(0, methodBuffer.length());

                //method start
                methodBuffer.append("{");
                String methodName = ctMethod.getName();
                CtClass ctResultType = ctMethod.getReturnType();
                String methodSignature = getCtMethodSignature(statementType, ctMethod);
                CtClass[] parameterTypes = ctMethod.getParameterTypes();
                int methodParameterSize = parameterTypes.length;

                //1: add start log
                if (methodParameterSize == 0) {
                    methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Execution,").append(methodSignature).append(",null,preparedSql,this);");
                } else {
                    methodBuffer.append("Object[]parameters = new Object[]{");
                    for (int i = 0; i < methodParameterSize; i++) {
                        if (i > 0) methodBuffer.append(",");
                        methodBuffer.append(getConvertType("$" + (i + 1), parameterTypes[i]));
                    }
                    methodBuffer.append("};");
                    methodBuffer.append("BeeMethodExecutionLog log = logCache.beforeCall(BeeMethodExecutionLog.Type_SQL_Execution,").append(methodSignature).append(",parameters,preparedSql,this);");
                }

                //2: add 'try' code snippet
                boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
                if (existsSQLException) methodBuffer.append("  try{");

                //3: set dirty flag
                methodBuffer.append("p.commitDirtyInd=!p.curAutoCommit;");

                //4: method call on raw object
                if (ctResultType == CtClass.voidType) {
                    methodBuffer.append(rawName).append(methodName).append("($$);");
                } else {
                    methodBuffer.append(ctResultType.getName()).append(" r=").append(rawName).append(methodName).append("($$);");
                }
                //5: update last access time
                methodBuffer.append("p.lastAccessTime=System.currentTimeMillis();");

                //6: add 'logCache.afterCall' code snippet
                if (ctResultType == CtClass.voidType) {
                    methodBuffer.append("logCache.afterCall(null,preparationTookTime,null,log);");
                } else {
                    methodBuffer.append("logCache.afterCall(").append(getConvertType("r", ctResultType)).append(",preparationTookTime,null,log);");
                }

                //7: return block
                if (ctResultType == ctResultSetClass) {
                    methodBuffer.append("return new ProxyResultSet(r,this,p);");
                } else if (ctResultType != CtClass.voidType) {
                    methodBuffer.append("return r;");
                }

                //8: catch block
                if (existsSQLException) {
                    methodBuffer.append(" }catch(SQLException e){");
                    methodBuffer.append(" p.checkSQLException(e);");
                    methodBuffer.append(" logCache.afterCall(e,preparationTookTime,null,log);");
                    methodBuffer.append(" throw e;}");
                }

                //method end
                methodBuffer.append("}");

                //add method to proxy class
                CtMethod newCtMethod = CtNewMethod.copy(ctMethod, statementProxyClass, null);
                newCtMethod.setBody(methodBuffer.toString());
                statementProxyClass.addMethod(newCtMethod);
            }//for end
        }
    }

    private static String getConvertType(String variableName, CtClass parameterType) {
        if (parameterType.isPrimitive()) {
            String typeName = parameterType.getName();
            if ("boolean".equals(typeName)) {
                return "Boolean.valueOf(" + variableName + ")";
            } else if ("byte".equals(typeName)) {
                return "Byte.valueOf(" + variableName + ")";
            } else if ("short".equals(typeName)) {
                return "Short.valueOf(" + variableName + ")";
            } else if ("int".equals(typeName)) {
                return "Integer.valueOf(" + variableName + ")";
            } else if ("long".equals(typeName)) {
                return "Long.valueOf(" + variableName + ")";
            } else if ("float".equals(typeName)) {
                return "Float.valueOf(" + variableName + ")";
            } else if ("double".equals(typeName)) {
                return "Double.valueOf(" + variableName + ")";
            } else if ("char".equals(typeName)) {
                return "Character.valueOf(" + variableName + ")";
            } else {
                return variableName;
            }
        } else {
            return variableName;
        }
    }

    //ctProxyDatabaseMetaDataClass,ctDatabaseMetaDataClass,ctDatabaseMetaDataSuperClass
    private static void addMethodsToProxyDatabaseMetaDataClass(ClassPool classPool, CtClass ctProxyDatabaseMetaDataClass, CtClass ctDatabaseMetaDataClass, CtClass ctDatabaseMetaDataSuperClass) throws Exception {
        List<CtMethod> linkedList = new ArrayList<>(50);
        HashSet<String> notNeedAddProxyMethods = findMethodsNotNeedProxy(ctDatabaseMetaDataSuperClass);
        ProxyClassesGenerator.resolveInterfaceMethods(ctDatabaseMetaDataClass, linkedList, notNeedAddProxyMethods);
        CtClass ctResultSetClass = classPool.get(ResultSet.class.getName());

        StringBuilder methodBuffer = new StringBuilder(40);
        for (CtMethod ctMethod : linkedList) {
            String methodName = ctMethod.getName();
            CtMethod newCtMethod = CtNewMethod.copy(ctMethod, ctProxyDatabaseMetaDataClass, null);
            newCtMethod.setModifiers(Modifier.PUBLIC);

            methodBuffer.delete(0, methodBuffer.length());
            methodBuffer.append("{")
                    .append("this.owner.checkClosed();");

            boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
            if (existsSQLException) methodBuffer.append("  try{");
            if (ctMethod.getReturnType() == ctResultSetClass) {
                methodBuffer.append("ResultSet r = raw.").append(methodName).append("($$);");
                methodBuffer.append("return r==null?null:new ProxyResultSet(r,p);");
            } else if (ctMethod.getReturnType() == CtClass.voidType) {
                methodBuffer.append("raw.").append(methodName).append("($$);");
            } else {
                methodBuffer.append("return raw.").append(methodName).append("($$);");
            }
            if (existsSQLException)
                methodBuffer.append(" }catch(SQLException e){ p.checkSQLException(e);throw e;}");

            methodBuffer.append("}");
            newCtMethod.setBody(methodBuffer.toString());
            ctProxyDatabaseMetaDataClass.addMethod(newCtMethod);
        }
    }

    private static void addOverrideMethodsForProxyResultSetClass(ClassPool classPool, CtClass ctResultSetClassProxyClass, CtClass ctResultSetClass, CtClass ctResultSetClassSuperClass) throws Exception {
        List<CtMethod> linkedList = new ArrayList<>(50);
        HashSet<String> notNeedAddProxyMethods = findMethodsNotNeedProxy(ctResultSetClassSuperClass);
        ProxyClassesGenerator.resolveInterfaceMethods(ctResultSetClass, linkedList, notNeedAddProxyMethods);
        CtClass ctResultSetMetaDataClass = classPool.get(ResultSetMetaData.class.getName());
        StringBuilder methodBuffer = new StringBuilder(25);

        for (CtMethod ctMethod : linkedList) {
            String methodName = ctMethod.getName();
            CtMethod newCtMethod = CtNewMethod.copy(ctMethod, ctResultSetClassProxyClass, null);
            newCtMethod.setModifiers(Modifier.PUBLIC);

            methodBuffer.delete(0, methodBuffer.length());
            methodBuffer.append("{");
            if (methodName.equals("close"))
                continue;

            boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
            if (existsSQLException) methodBuffer.append("  try{");
            if (methodName.startsWith("insert") || methodName.startsWith("update") || methodName.startsWith("delete")) {
                if (ctMethod.getReturnType() == CtClass.voidType) {
                    methodBuffer.append("raw.").append(methodName).append("($$);").append(" p.updateAccessTime();");
                } else {
                    methodBuffer.append(ctMethod.getReturnType().getName()).append(" r=raw.").append(methodName).append("($$);")
                            .append(" p.updateAccessTime();").append(" return r;");
                }
            } else {
                CtClass ctReturnType = ctMethod.getReturnType();
                if (ctReturnType == ctResultSetMetaDataClass) {
                    methodBuffer.append("return new ProxyResultSetMetaData(raw.").append(methodName).append("($$),this,p);");
                } else if (ctReturnType == CtClass.voidType) {
                    methodBuffer.append("raw.").append(methodName).append("($$);");
                } else {
                    methodBuffer.append("return raw.").append(methodName).append("($$);");
                }
            }
            if (existsSQLException)
                methodBuffer.append("  }catch(SQLException e){ p.checkSQLException(e);throw e;}");

            methodBuffer.append("}");
            newCtMethod.setBody(methodBuffer.toString());
            ctResultSetClassProxyClass.addMethod(newCtMethod);
        }
    }

    private static void addOverrideMethodsForProxyResultSetMetaDataClass(CtClass ctProxyResultSetMetaDataClass, CtClass ctResultSetMetaDataClass, CtClass ctResultSetMetaDataSuperClass) throws Exception {
        List<CtMethod> linkedList = new ArrayList<>(50);
        HashSet<String> notNeedAddProxyMethods = findMethodsNotNeedProxy(ctResultSetMetaDataSuperClass);
        ProxyClassesGenerator.resolveInterfaceMethods(ctResultSetMetaDataClass, linkedList, notNeedAddProxyMethods);

        StringBuilder methodBuffer = new StringBuilder(40);
        for (CtMethod ctMethod : linkedList) {
            String methodName = ctMethod.getName();
            CtMethod newCtMethod = CtNewMethod.copy(ctMethod, ctProxyResultSetMetaDataClass, null);
            newCtMethod.setModifiers(Modifier.PUBLIC);

            methodBuffer.delete(0, methodBuffer.length());
            methodBuffer.append("{")
                    .append("this.owner.checkClosed();");

            boolean existsSQLException = exitsSQLException(ctMethod.getExceptionTypes());
            if (existsSQLException) methodBuffer.append("  try{");
            if (ctMethod.getReturnType() == CtClass.voidType) {
                methodBuffer.append("raw.").append(methodName).append("($$);");
            } else {
                methodBuffer.append("return raw.").append(methodName).append("($$);");
            }
            if (existsSQLException)
                methodBuffer.append(" }catch(SQLException e){ p.checkSQLException(e);throw e;}");

            methodBuffer.append("}");
            newCtMethod.setBody(methodBuffer.toString());
            ctProxyResultSetMetaDataClass.addMethod(newCtMethod);
        }
    }

    private static boolean exitsSQLException(CtClass[] exceptionTypes) {
        if (exceptionTypes == null) return false;
        for (CtClass exceptionClass : exceptionTypes) {
            if ("java.sql.SQLException".equals(exceptionClass.getName()))
                return true;
        }
        return false;
    }

    private static String getCtMethodSignature(String methodOwer, CtMethod method) throws Exception {
        StringBuilder builder = new StringBuilder(20);
        builder.append("\"").append(methodOwer).append(method.getName()).append("(");
        CtClass[] paramTypes = method.getParameterTypes();
        for (int i = 0, l = paramTypes.length; i < l; i++) {
            if (i > 0) builder.append(",");
            builder.append(paramTypes[i].getName());
        }
        builder.append(")").append("\"");
        return builder.toString();
    }

    private static void resolveInterfaceMethods(CtClass interfaceClass, List<CtMethod> linkedList, HashSet<String> exitSignatureSet) throws Exception {
        for (CtMethod ctMethod : interfaceClass.getDeclaredMethods()) {
            int modifiers = ctMethod.getModifiers();
            String signature = ctMethod.getName() + ctMethod.getSignature();
            if (Modifier.isAbstract(modifiers)
                    && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))
                    && !Modifier.isStatic(modifiers)
                    && !Modifier.isFinal(modifiers)
                    && !exitSignatureSet.contains(signature)) {

                linkedList.add(ctMethod);
                exitSignatureSet.add(signature);
            }
        }

        for (CtClass superInterface : interfaceClass.getInterfaces())
            ProxyClassesGenerator.resolveInterfaceMethods(superInterface, linkedList, exitSignatureSet);
    }

    private static void getConnectionStatementMethods(ClassPool classPool, CtClass ctConnectionInterfaceClass, List<CtMethod> linkedList) throws Exception {
        CtClass ctStatementClass = classPool.get(Statement.class.getName());
        CtClass ctPreparedStatementClass = classPool.get(PreparedStatement.class.getName());
        CtClass ctCallableStatementClass = classPool.get(CallableStatement.class.getName());

        for (CtMethod ctMethod : ctConnectionInterfaceClass.getMethods()) {
            int modifiers = ctMethod.getModifiers();

            if (Modifier.isAbstract(modifiers)
                    && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))
                    && !Modifier.isStatic(modifiers)
                    && !Modifier.isFinal(modifiers)) {

                CtClass ctResultType = ctMethod.getReturnType();
                if (ctResultType == ctStatementClass || ctResultType == ctPreparedStatementClass || ctResultType == ctCallableStatementClass) {
                    linkedList.add(ctMethod);
                }
            }
        }
    }

    private static void getStatementExecutionMethods(CtClass ctStatementInterfaceClass, List<CtMethod> linkedList) {
        for (CtMethod ctMethod : ctStatementInterfaceClass.getMethods()) {
            String methodName = ctMethod.getName();
            if (!"executeBatch".equals(methodName) && !"executeLargeBatch".equals(methodName) && methodName.startsWith("execute")) {
                linkedList.add(ctMethod);
            }
        }
    }
}

